﻿using LdtCommonLibraryHttp;
using LdtCommonUtil;
using LdtCommonUtil.Security;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NlpApp.Model;
using NlpApp.Model.RexUninlu;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using static FreeSql.Internal.GlobalFilter;

namespace NlpApp.Bll
{
    public static class Ext
    {
        /// <summary>
        /// 将字典集合转换为动态对象
        /// </summary>
        /// <param name="propertyDics">属性/属性值字典集合</param>
        /// <returns></returns>
        public static dynamic DictionaryToExpandoObject(this IDictionary<string, object> propertyDics)
        {
            if (propertyDics == null) return null;

            IDictionary<string, object> expandoObject = new ExpandoObject();
            foreach (var d in propertyDics)
            {
                expandoObject.Add(d);
            }

            return expandoObject as ExpandoObject;
        }
    }

    public class ServiceBll
    {
        /// <summary>
        /// The XML configs Tuple item标识是否换行处理
        /// </summary>
        private static readonly ConcurrentDictionary<string, List<UieModelConfigList>> xmlConfigs = new ConcurrentDictionary<string, List<UieModelConfigList>>();

        private readonly string extractUrl;
        private readonly IHttpHelper httpHelper;
        private readonly ILogger<ServiceBll> logger;
        /// <summary>
        /// The maximum con current number最大并发数
        /// </summary>
        private readonly int maxConCurrentNum;
        /// <summary>
        /// The noe con current number当前并发数
        /// </summary>
        private int nowConCurrentNum = 0;

        public ServiceBll(ILogger<ServiceBll> logger, IHttpHelper httpHelper, IConfiguration configuration)
        {
            this.logger = logger;
            this.httpHelper = httpHelper;
            extractUrl = configuration["ExtractUrl"];
            maxConCurrentNum = Convert.ToInt16(configuration["MaxConCurrentNum"] ?? "20");
        }

        /// <summary>
        /// Extracts the specified text.从文本中提取信息
        /// </summary>
        /// <param name="texts">The text.文本内容数组</param>
        /// <param name="parameters">The parameters.提取参数 实体抽取["姓名","性别"];关系抽取['法院', {'原告': '委托代理人'}, {'被告': '委托代理人'}] ;事件抽取 {'地震触发词': ['地震强度', '时间', '震中位置', '震源深度']} ;句子级情感倾向分类 情感倾向[正向，负向] </param>
        /// <returns></returns>
        /// <exception cref="ServiceException">$"请求提取接口失败:{await r1.Content.ReadAsStringAsync()}</exception>
        public async Task<object> Extract(NlpExtractModel extractModel)
        {
            return new JRaw(await GetUieStrResult(extractModel));
        }

        /// <summary>
        /// Extracts the specified text.从文本中提取信息
        /// </summary>
        /// <param name="texts">The text.文本内容数组</param>
        /// <param name="parameters">The parameters.提取参数 实体抽取["姓名","性别"];关系抽取['法院', {'原告': '委托代理人'}, {'被告': '委托代理人'}] ;事件抽取 {'地震触发词': ['地震强度', '时间', '震中位置', '震源深度']} ;句子级情感倾向分类 情感倾向[正向，负向] </param>
        /// <returns></returns>
        /// <exception cref="ServiceException">$"请求提取接口失败:{await r1.Content.ReadAsStringAsync()}</exception>
        public async Task<List<RexUninluInfolistItemResult>> GetUieRexExtract(EntityRequest extractModel)
        {
            var c1 = GetUieModelConfigs(extractModel.XmlConfig);
            List<RexUninluInfolistItemResult> result = new List<RexUninluInfolistItemResult>();
            foreach (var a in c1)
            {
                List<string> contents = new List<string>();
                if (a.Isline == "true")
                {
                    contents = extractModel.Content.Split("\r\n".ToArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
                }
                else
                {
                    contents = new List<string> { extractModel.Content };
                }
                if (!string.IsNullOrEmpty(a.Regexmate))
                {
                    //匹配正则
                    contents = contents.Select(p => Regex.IsMatch(p, a.Regexmate) ? p : "").ToList();
                }
                List<object> schemaslists = new List<object>();
                a.NodesObj.ForEach(p =>
                {
                    if (!string.IsNullOrEmpty(p.Name))
                    {
                        var sub1 = new Dictionary<string, object>();
                        if (p.NodesObj.Exists(p => p.PType == "reletion"))
                        {
                            sub1.Add(p.Name, p.NodesObj?.Where(p => p.PType == "reletion").Select(aa => aa.Name).ToList());
                            schemaslists.Add(sub1.DictionaryToExpandoObject());
                        }
                        else
                        {
                            schemaslists.Add(p.Name);
                        }
                    }
                });
                var nlpExtractModel = new NlpExtractModel
                {
                    Schemas = schemaslists.Distinct(),
                    Texts = contents,
                };
                var r1 = await GetUieByUrl(nlpExtractModel);
                var temp1 = GetRexUninResult(a.NodesObj, r1);
                result.AddRange(temp1);
            }
            //分组配置提取结果
            //return GetUieStartEndResult(c1, extractModel.Content, r1);//
            return result;
        }
        /// <summary>
        /// Gets the value by configuration.先全部补全code
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="t1">The t1.</param>
        /// <returns></returns>
        private void CompletionCode(UieModelConfig item, List<RexUninluInfolistItemResult> t1)
        {
            t1.Where(p => p.name == item.Name).ToList().ForEach(p =>
            {
                p.code = item.Code;
                p.relations = GetSubRelation(p.relations, item);
            });
        }
        /// <summary>
        /// Converts the dic to rex.转换数据格式
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="line">The line.</param>
        /// <returns></returns>
        public List<RexUninluInfolistItemResult> ConvertDicToRex(Dictionary<string, List<UieEntityResult>> item, int line)
        {
            return item?.SelectMany(p => p.Value.Select(a => new RexUninluInfolistItemResult
            {
                name = p.Key,
                span = a.text,
                offset = new List<int> { a.start, a.end },
                probability = a.probability,
                line = line + 1,
                relations = ConvertDicToRex(a.relations, line)
            })).ToList();
        }

        private UieModelConfig GetNodeObj(XElement p)
        {
            UieModelConfig uieModelConfig = new UieModelConfig();
            uieModelConfig.Name = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Name", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Mustbehindname = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Mustbehindname", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Code = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Code", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Extracttype = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Extracttype", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Value = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Value", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Value = uieModelConfig.Value ?? uieModelConfig.Name;
            uieModelConfig.Result = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Result", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Priority = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Priority", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Key = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Key", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.Regexextract = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Regexextract", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.PType = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("PType", StringComparison.OrdinalIgnoreCase))?.Value;
            uieModelConfig.NodesObj = p.Elements("node").Select(a => { return GetNodeObj(a); }).ToList();
            return uieModelConfig;
        }

        /// <summary>
        /// 对结果做一次正则处理
        /// </summary>
        /// <param name="result">需要处理的结果数据</param>
        /// <param name="regexextract">提取正则</param>
        /// <returns></returns>
        private string GetRegexExtractResult(string result, string regexextract)
        {
            return string.IsNullOrEmpty(regexextract) ? result : Regex.Match(result, regexextract).Groups.Values.FirstOrDefault()?.Value;
        }

        /// <summary>
        /// Gets the rex unin result.
        /// </summary>
        /// <param name="config">The configuration.</param>
        /// <param name="uieresults">The uieresult.</param>
        /// <returns></returns>
        private List<RexUninluInfolistItemResult> GetRexUninResult(List<UieModelConfig> config, List<Dictionary<string, List<UieEntityResult>>> uieresults)
        {
            var t1 = uieresults.SelectMany((item, line) => ConvertDicToRex(item, line).ToList()).ToList();
            config.ForEach(a =>
            {
                CompletionCode(a, t1);
            });
            PreDealData(t1);
            //相同结果情况下取高可信度的结果(这一块存疑，如果本身就是多实体类型，如果后续存在需要加组key判断)
            var r1 = t1.GroupBy(p => $"{p.line}{p.offset[0]}{p.offset[1]}").
                Select(p => p.OrderByDescending(a => a.probability).First()).ToList();
            var results = config.SelectMany(item =>
            {
                var t1 = GetValueByConfig(item, r1, null);
                return t1;
            });
            return results.OrderBy(p => p.line).ThenBy(p => p.code).ToList();
        }
        /// <summary>
        /// Gets the sub relation.补全code
        /// </summary>
        /// <param name="itemResults">The item results.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private List<RexUninluInfolistItemResult> GetSubRelation(List<RexUninluInfolistItemResult> itemResults, UieModelConfig item)
        {
            itemResults?.ForEach((p) =>
            {
                p.code = item.NodesObj.Find(a => a.Name == p.name)?.Code;
            });
            return itemResults;
        }

        /// <summary>
        /// Gets the uie by URL.从python uie接口中获取数据
        /// </summary>
        /// <param name="extractModel">The extract model.</param>
        /// <returns></returns>
        /// <exception cref="ServiceException">
        /// $"请求提取接口失败:{t1.Result.Message}
        /// or
        /// $"请求提取接口失败:{await r1.Content.ReadAsStringAsync()}
        /// </exception>
        public async Task<List<Dictionary<string, List<UieEntityResult>>>> GetUieByUrl(NlpExtractModel extractModel)
        {
            var t1 = JsonConvert.DeserializeObject<HttpReturnEntity2>(await GetUieStrResult(extractModel));
            if (t1.Result.Code == "1")
            {
                var t2 = t1.Body as JArray;
                var uieResults = t2.Select(p => (p as JObject).ToObject<Dictionary<string, List<UieEntityResult>>>()).ToList();
                return uieResults;
            }
            else
            {
                throw new ServiceException($"请求提取接口失败:{t1.Result.Message}");
            }
        }
        /// <summary>
        /// Gets the uie model configs.
        /// </summary>
        /// <param name="base64Config">The base64 configuration.</param>
        /// <returns>item1是标识是否换行处理，2标识具体配置</returns>
        private List<UieModelConfigList> GetUieModelConfigs(string base64Config)
        {
            return xmlConfigs.GetOrAdd(base64Config.MDString(), (a) =>
            {
                var x1 = new System.Text.StringBuilder(base64Config.Base64Decrypt());
                XDocument xml = XDocument.Parse(x1.ToString());

                var nodesXml = xml.Root.Elements("nodes");
                var nodes = nodesXml.Select(p =>
                {
                    UieModelConfigList uieModelConfigList = new UieModelConfigList();
                    uieModelConfigList.Isline = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Isline", StringComparison.OrdinalIgnoreCase))?.Value;
                    uieModelConfigList.Regexmate = p.Attributes().FirstOrDefault(a => a.Name.LocalName.EndsWith("Regexmate", StringComparison.OrdinalIgnoreCase))?.Value;
                    var subNodes = p.Elements("node");
                    uieModelConfigList.NodesObj = subNodes.Select(pp => GetNodeObj(pp)).ToList();
                    return uieModelConfigList;
                }).ToList();
                return nodes;
            });
        }
        /// <summary>
        /// Gets the uie result.获取start-end格式字符串
        /// </summary>
        /// <param name="config">The configuration.</param>
        /// <param name="content">The content.</param>
        /// <param name="uieResults">The uie results.</param>
        /// <returns></returns>
        private string GetUieStartEndResult(List<UieModelConfig> config, string content, Dictionary<string, List<UieEntityResult>> uieResults)
        {
            List<UieEntityResult> okUieEntityResults = new List<UieEntityResult>();
            RemoveSameKey(uieResults, config);
            foreach (var item in config)
            {
                //判断取值有+没有
                var vals = item.Value.Split(",，".ToArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (var code in vals)
                {
                    if (uieResults.TryGetValue(code, out var result))
                    {
                        if (!result.Any())
                        {
                            continue;
                        }
                        else
                        {
                            if (item.Extracttype == "classify")
                            {
                                ////content += $"{item.code}start{item.code}end";
                                break;
                            }
                        }
                        result = result.Select(p => new UieEntityResult
                        {
                            //code = item.code,
                            end = p.end,
                            probability = p.probability,
                            start = p.start,
                            text = p.text,
                        }).ToList();
                        //判断类型
                        switch (item.Extracttype)
                        {
                            default:
                                var a1 = result.OrderBy(p => p.start).First();
                                okUieEntityResults.Add(a1);
                                break;

                            case "all":
                                okUieEntityResults.AddRange(result);
                                break;
                        }
                    }
                }
            }
            var rr1 = okUieEntityResults.OrderByDescending(p => p.start);
            foreach (var r in rr1)
            {
                content = content.Insert(r.end, $"{r.code}end");
                content = content.Insert(r.start, $"{r.code}start");
            }
            return content;
        }

        /// <summary>
        /// Gets the uie string result.需要设置最大并发数
        /// </summary>
        /// <param name="extractModel">The extract model.</param>
        /// <returns></returns>
        /// <exception cref="ServiceException">$"请求提取接口失败:{await r1.Content.ReadAsStringAsync()}</exception>
        private async Task<string> GetUieStrResult(NlpExtractModel extractModel)
        {
            int overNum = 0;
            while (nowConCurrentNum >= maxConCurrentNum)
            {
                overNum++;
                if (overNum >= 600)
                {
                    throw new ServiceException($"等待请求超时");
                }
                await Task.Delay(1000);
            }
            try
            {
                nowConCurrentNum++;
                if (extractModel.Texts is string)
                {
                    extractModel.Texts = Strings.Left(extractModel.Texts.ToString(), 512);
                }
                //else
                //{
                //    var arr1 = extractModel.Texts as JArray;
                //    extractModel.Texts = arr1.Select(p => Strings.Left(p.Value<string>(), 512)).ToList();
                //}
                var r1 = await httpHelper.PostAsync(extractUrl, JsonConvert.SerializeObject(extractModel), 300000);
                if (r1.IsSuccessStatusCode)
                {
                    return await r1.Content.ReadAsStringAsync();
                }
                else
                {
                    throw new ServiceException($"请求提取接口失败:{await r1.Content.ReadAsStringAsync()}");
                }
            }
            finally
            {
                nowConCurrentNum--;
            }
        }
        /// <summary>
        /// 根据节点配置获取配置节点对应的值
        /// </summary>
        /// <param name="item">node节点配置</param>
        /// <param name="t1">原始提取出来的所有数据</param>
        /// <param name="parentValue">父节点的值</param>
        /// <returns></returns>
        private List<RexUninluInfolistItemResult> GetValueByConfig(UieModelConfig item, List<RexUninluInfolistItemResult> t1, RexUninluInfolistItemResult parentValue)
        {
            var rr1 = new List<RexUninluInfolistItemResult>();
            if (t1 == null)
            {
                return rr1;
            }
            //判断取值类型
            switch (item.Extracttype)
            {
                case "default"://固定值 必然是子节点，父节点是错误的
                    rr1 = new List<RexUninluInfolistItemResult> { new RexUninluInfolistItemResult
                                    {
                                        code = item.Code,
                                        span = item.Result,
                                        line =  parentValue.line,
                                        offset = parentValue.offset,
                                     }
                     };
                    break;
                case "classify"://分类输出，要求result属性 没有默认是、否
                    var valkeys = item.Value.Split(",");
                    var outR1 = (item.Result ?? "是,否").Split(",");
                    var v0 = t1.Find(p => valkeys.Contains(p.name));
                    if (v0 != null)
                    {
                        rr1 = rr1.Select(cc => new RexUninluInfolistItemResult
                        {
                            code = cc.code,
                            offset = v0.offset,
                            span = outR1[0],
                            line = cc.line,
                            relations = item.NodesObj?.SelectMany(n1 => GetValueByConfig(n1, t1, v0)).ToList()
                        }).ToList();
                    }
                    else
                    {
                        rr1 = rr1.Select(cc => new RexUninluInfolistItemResult
                        {
                            code = cc.code,
                            span = outR1[1],
                            offset = new List<int> { -1, -1 },
                        }).ToList();
                    }
                    break;
                case "distinctlist"://去重列表输出
                    var v10 = t1.Where(p => p.name == item.Name).Distinct(p => p.span);
                    if (v10.Any())
                    {
                        rr1 = new List<RexUninluInfolistItemResult> { new RexUninluInfolistItemResult() };
                        rr1[0].relations = v10.SelectMany((cc, index) =>
                        {
                            List<RexUninluInfolistItemResult> rexUninluInfolistItemResults = new List<RexUninluInfolistItemResult>
                            {
                                new RexUninluInfolistItemResult
                                {
                                    code = item.Code,
                                    offset = cc.offset,
                                    span =GetRegexExtractResult(cc.span,item.Regexextract),
                                     line = cc.line,
                                      relations=GetSubRelations(item.NodesObj,cc,t1),
                                }
                            };
                            return rexUninluInfolistItemResults;
                        }).ToList();
                    }
                    break;
                case "check"://以,分割合并输出
                    var v100 = t1.Where(p => p.name == item.Name).ToList();
                    if (v100.Any())
                    {
                        var pp1 = new RexUninluInfolistItemResult
                        {
                            code = item.Code,
                            offset = v100[0].offset,
                            span = string.Join(",", v100.Select(aaa => GetRegexExtractResult(aaa.span, item.Regexextract))),
                            line = v100[0].line,
                        };
                        rr1.Add(pp1);
                    }
                    break;
                default://只取第一个值
                    var v1 = t1.Find(p => p.name == item.Name);
                    if (v1 != null)
                    {
                        var pp1 = new RexUninluInfolistItemResult
                        {
                            code = item.Code,
                            offset = v1.offset,
                            span = GetRegexExtractResult(v1.span, item.Regexextract),
                            line = v1.line,
                            relations = item.NodesObj?.SelectMany(n1 => GetValueByConfig(n1, t1, v1)).ToList()
                        };
                        rr1.Add(pp1);
                    }
                    break;
            }
            return rr1;
        }
        /// <summary>
        /// Gets the sub relations.获取节点的子relations
        /// </summary>
        /// <param name="subConfigs">The sub configs.子节点配置</param>
        /// <param name="node">The node.当前节点值</param>
        /// <param name="t1">The t1.所有的值</param>
        /// <returns></returns>
        private List<RexUninluInfolistItemResult> GetSubRelations(List<UieModelConfig> subConfigs, RexUninluInfolistItemResult node, List<RexUninluInfolistItemResult> t1)
        {
            if (subConfigs != null && subConfigs.Any())
            {
                var sub2 = subConfigs.Where(p => p.PType != "reletion").SelectMany(n1 => GetValueByConfig(n1, t1, node)).ToList();
                if (node.relations != null && node.relations.Any())
                {
                    var sub1 = subConfigs.Where(p => p.PType == "reletion").SelectMany(n1 => GetValueByConfig(n1, node.relations, node)).ToList();
                    sub2.AddRange(sub1);
                }
                return sub2;
            }
            return new List<RexUninluInfolistItemResult>();
        }
        /// <summary>
        /// 预处理结果.移除已经在关系中存在的\合并相同的节点输出和关系\关联多重关系
        /// </summary>
        /// <param name="results">The results.</param>
        /// <returns></returns>
        private void PreDealData(List<RexUninluInfolistItemResult> results)
        {
            var allrelations = results.Where(p => p.relations != null).SelectMany(p => p.relations).ToList();
            for (int i = 0; i < results.Count; i++)
            {
                var item = results[i];
                ////判断是其他节点的子节点--关联过去并删除
                var subRelations = allrelations.FindAll(a => a.name == item.name && a.offset?[0] == item.offset[0] && a.offset?[1] == item.offset[1]);
                if (subRelations.Any())
                {
                    subRelations.ForEach(a =>
                    {
                        a.relations = item.relations;
                    });
                    results.RemoveAt(i);
                    i--;
                }
            }
            //先合并相同节点的子节点
            //合并相同的节点值输出和关系
            ////p.relations = results.Where(a => a.name == p.name && a.offset?[0] == p.offset[0] && a.offset?[1] == p.offset[1])
            ////    .Where(p => p.relations != null).SelectMany(p => p.relations).ToList();
            ////var cc1 = results.RemoveAll(a => a != p && a.name == p.name && a.offset?[0] == p.offset[0] && a.offset?[1] == p.offset[1]);
            MergeRelation(results);
        }
        /// <summary>
        /// Merges the relation.合并相同code和值
        /// </summary>
        /// <param name="results">The results.</param>
        /// <returns></returns>
        private List<RexUninluInfolistItemResult> MergeRelation(List<RexUninluInfolistItemResult> results)
        {
            for (int i = 0; i < results.Count; i++)
            {
                var result = results[i];
                result.relations = results.Where(a => a.code == result.code && a.span == result.span)
                   .Where(b => b.relations != null).SelectMany(b => b.relations).ToList();
                var cc1 = results.RemoveAll(a => a != result && a.code == result.code && a.span == result.span);
                ////i = i - cc1;
                result.relations = MergeRelation(result.relations);
            }
            return results;
        }


        /// <summary>
        /// Removes the same key.
        /// </summary>
        /// <param name="uieResults">The uie results.</param>
        /// <param name="config">The configuration.</param>
        /// <returns></returns>
        private void RemoveSameKey(Dictionary<string, List<UieEntityResult>> uieResults, List<UieModelConfig> config)
        {
            //判断是否有相同存在的key并且相同结果的取优先级高的,去除低优先级数据
            var groupVals = uieResults.SelectMany(p =>
            p.Value.Select(a => new UieEntityResult
            {
                code = p.Key,
                end = a.end,
                probability = a.probability,
                start = a.start,
                text = a.text,
            })).GroupBy(p => $"{p.start}{p.end}{p.text}").Where(p => p.Count() > 1).ToList();
            foreach (var item in groupVals)
            {
                //出现的都是重复的了
                var temp1 = item.Select(p => p.code).Select(p =>
                    config.First(a => a.Name == p));
                var groupKeyconfigs = temp1.GroupBy(p => p.Key);
                var rml = (groupKeyconfigs.SelectMany(gg1 => gg1.OrderByDescending(p => p.Priority).Skip(1)));
                foreach (var rItem in uieResults)
                {
                    rItem.Value.RemoveAll(p => item.Key == $"{p.start}{p.end}{p.text}"
                        && rml.Any(a => a.Name.Contains(rItem.Key)));
                }
            }
        }
    }
}